/*
 * Copyright (c) 2010 Network Security Lab (NSL), Department of Electrical
 *                    Engineering, University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: He Wu mdzz@uw.edu
 */

/**
 * @file socket.c
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "socket.h"

#define socket_debug 0

/******************************************************************************/

// some global
struct sockaddr_in s_client;  // socket info about machine connecting
struct sockaddr_in s_server;  //
int socksize = sizeof(struct sockaddr_in);
static int s = 0;             //!< socket
static const __u8 GUI_DATA    = 0;
static const __u8 GUI_ACK     = 1;
static const __u8 GUI_PROG    = 2;
static const __u8 GUI_DONE    = 3;
static const __u8 GUI_ERROR   = 4;
static const __u8 GUI_ROUTING = 5;
static const char const *GUI_ADDR = GUI_IP; // this is awesome
static pthread_mutex_t data_socket_lock = PTHREAD_MUTEX_INITIALIZER;  //!< data socket lock
int sendCommand = 0;  //!< command flag
int doneSending = 0;  //!< done sending flag
pthread_mutex_t sendCmdStatusLock = PTHREAD_MUTEX_INITIALIZER; //!< flag mutex

// external
extern __s8 total_cmds;   //!< total number of commands to be sent
extern __s8 sent_cmds;    //!< number of commands sent

/******************************************************************************/

void *cmdSocket(__u8 *cmd_list)
{
  __u8 buf[BUFFER_LEN];   // socket data buffer

  printf("Listening for command packet from GUI.\n\n");
  // socket setup
  if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
    perror("\nFailed to create CMD socket\n\n");
    exit(-1);
  }
  memset(&s_server, 0, sizeof(s_server)); // zero out server struct before filling out again
  s_server.sin_family = AF_INET;          // Set to TCP/IP
  s_server.sin_addr.s_addr = INADDR_ANY;  // Allow any interface
  s_server.sin_port = htons(CMD_PORT);    // set server to port PORT;
  int binderr = bind(s, (struct sockaddr *) &s_server, socksize);
  if (binderr < 0) {
    perror("\nSocket bind error!\n");
    exit(-1);
  }
  // Set up client socket (IP address and port # of GUI is hard-coded here)
  memset(&s_client, 0, sizeof(s_client)); // zero out destination struct before filling out again
  s_client.sin_family = AF_INET;          // set to TCP/IP
  s_client.sin_addr.s_addr = inet_addr(GUI_ADDR); // connect to GUI by default
  s_client.sin_port = htons(DATA_PORT);   // set client port # to DATA_PORT;

  // main loop
  while (1) {
    // wait for command from GUI
    if (!sendCommand) {
      // send DONE message back to GUI
      if (doneSending) {
        if (sendto(s, &GUI_DONE, 1, 0, (struct sockaddr *) &s_client, socksize) == -1)
          printf("Send back to GUI failed!\n");
      }

      // receive commands from GUI
      if (recvfrom(s, buf, BUFFER_LEN, 0, (struct sockaddr *) &s_client, &socksize) != -1) {
        fprintf(stderr, "Received packet from %s:%d\nData: %s\n\n",
                inet_ntoa(s_client.sin_addr), ntohs(s_client.sin_port), buf);
        // done receiving data from GUI, send commands out
        pthread_mutex_lock(&sendCmdStatusLock);
        sendCommand = 1;  // set flag for sending commands
        doneSending = 0;  // done sending flag is set in main code
        memcpy(cmd_list, buf, BUFFER_LEN);  // get command list
        pthread_mutex_unlock(&sendCmdStatusLock);
      }

      // update # of commands to be sent
      total_cmds = buf[0];

      // send ACK back to GUI
      if (sendto(s, &GUI_ACK, 1, 0, (struct sockaddr *) &s_client, socksize) == -1)
        printf("Send back to GUI failed!\n");
    }
    // send command out
    else {
      if (socket_debug) {
        printf("Sending commands!\n");
        printf("Total # of commands is: %d, %d sent.\n", total_cmds, sent_cmds);
      }

      // build respond packet to GUI
      memcpy(buf, &GUI_PROG, 1);
      memcpy(buf + 1, &sent_cmds, 1);
      memcpy(buf + 2, &total_cmds, 1);

      // send progress packet back to GUI
      if (sendto(s, buf, 3, 0, (struct sockaddr *) &s_client, socksize) == -1)
        printf("Send cmd sending progress back to GUI failed!\n");

      // wait a little before receiving next command stream
      usleep(SOCKET_UPDATE_INTEVAL);
    }
  } // end while(1)

  close(s); // close socket
}


int dataSocket(packet in_pkt)
{
  /*
   * Entering critical section, only one piece of data can be sent to base node
   * at a time.
   */
  pthread_mutex_lock(&data_socket_lock);

  __u8 buf[CMD_PKT_SIZE + 1]; // socket data buffer

  // build packet content
  memcpy(buf, &GUI_DATA, 1);
  memcpy(buf + 1, in_pkt.data, CMD_PKT_SIZE);

  //send localization data back to GUI
  if (sendto(s, buf, CMD_PKT_SIZE + 1, 0, (struct sockaddr *) &s_client, socksize) == -1) {
    pthread_mutex_unlock(&data_socket_lock);
    printf("Send localization/health data back to GUI failed!\n");
    return -1;
  }

  /*
   * Leaving critical section, only one piece of data can be sent to base node
   * at a time.
   */
  pthread_mutex_unlock(&data_socket_lock);

  printf("\nDone sending localization/health data to GUI.\n\n");

  if (socket_debug) {
    int i = 0;
    for (i = 0; i < CMD_PKT_SIZE + 1; i++) {
      printf("Data packet byte %d is %d\n", i, buf[i]);
    }
    printf("\n\n");
  }
  return 0;
}


int errorSocket(packet in_pkt)
{
  __u8 buf[CMD_PKT_SIZE + 1]; // socket data buffer

  printf("\nSending error packet to GUI.\n\n");

  // build packet content
  memcpy(buf, &GUI_ERROR, 1);
  memcpy(buf + 1, in_pkt.data, CMD_PKT_SIZE);

  //send localization data back to GUI
  if (sendto(s, buf, CMD_PKT_SIZE + 1, 0, (struct sockaddr *) &s_client, socksize) == -1) {
    printf("Send error packet back to GUI failed!\n");
    return -1;
  }
  return 0;
}


int routingSocket(__u16 *routingTable, __u8 routingTable_size)
{
  __u8 buf[BUFFER_LEN]; // socket data buffer

  printf("\nSending routing table to GUI.\n\n");

  // build packet content
  memcpy(buf, &GUI_ROUTING, 1);
  memcpy(buf + 1, &routingTable_size, 1);
  memcpy(buf + 2, routingTable, routingTable_size * 2);

  //send localization data back to GUI
  if (sendto(s, buf, BUFFER_LEN, 0, (struct sockaddr *) &s_client, socksize) == -1) {
    printf("Send localization data back to GUI failed!\n");
    return -1;
  }
  return 0;
}
